package com.mjk.common.core.jdbc.util;

import com.mjk.common.base.model.BaseEntity;
import com.mjk.common.base.model.BaseExample;
import com.mjk.common.core.jdbc.annotation.ArcSharding;
import com.mjk.common.core.jdbc.annotation.ExampleField;
import com.mjk.common.core.jdbc.annotation.Filed;
import com.mjk.common.core.jdbc.bean.Criterion;
import com.mjk.common.core.jdbc.util.provider.SqlProvider;
import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.util.ClassUtil;
import com.mjk.common.util.StrUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.List;
import java.util.Objects;

/**
 * example生成类
 */
public class ExampleBuilderUtil {
    private static final String END_PRIFIX = ";\r\n";
    private static final String PRIFIX = "\r\n";
    private static final String SPACE = "   ";

    public static void buildExampleFile(Class<? extends BaseEntity> entity) {
        ArcSharding sharding = ShardingUtils.getArcSharding(entity);
        if(null == sharding) {
            return;
        }

        String entityName = entity.getName();
        String filePath = Objects.requireNonNull(entity.getResource("")).getPath();
        String fileName = entityName.substring(entityName.lastIndexOf(46) + 1) + "Example.java";
        fileName = filePath + fileName;
        if (fileName.contains("target/classes/")) {
            fileName = fileName.replace("target/classes/", "src/main/java/");
        }
        if (fileName.contains("target/test-classes/")) {
            fileName = fileName.replace("target/test-classes/", "src/test/java/");
        }
        File f = new File(fileName);
        if (f.exists()) {
            try {
                Files.delete(f.toPath());
            }catch(Exception e){
                BizLogger.error(e);
            }
        }
        try(FileWriter fw = new FileWriter(f);) {
            if(f.createNewFile()) {
            	fw.write(buildTitle(entity));
            }
        } catch (IOException e) {
           //do noting
        }
    }

    public static String buildTitle(Class<?> entity) {
        StringBuilder sb = new StringBuilder();
        String entityName = entity.getName();
        sb.append("package ").append(entityName.substring(0, entityName.lastIndexOf(46))).append(";\r\n");
        List<Field> fields = SqlProvider.getValidTableFiled(entity);//entity.getDeclaredFields();
        for (Field f : fields) {
        	ExampleField exampleField = f.getAnnotation(ExampleField.class);
        	if(null == exampleField){
        		continue;
        	}
            if (!ClassUtil.isBasicType(f.getType())) {
                String type = f.getGenericType().toString().replace("class ", "").replace("interface", "");
                sb.append("import ").append(type).append(";\r\n");
            }
        }
        sb.append("import ").append(Criterion.class.getName()).append(";\r\n");
        sb.append("import java.io.Serializable;\r\n");
        sb.append("import java.util.ArrayList;\r\n");
        sb.append("import java.util.List;\r\n");
        sb.append("import java.util.Map;\r\n");
        sb.append("import java.util.HashMap;\r\n");
        sb.append("import com.mjk.common.util.ReflectionUtils;\r\n");
        sb.append("import ").append(BaseExample.class.getName()).append(";\r\n");
        String clazzName = entityName.substring(entityName.lastIndexOf(46) + 1) + "Example";
        sb.append("public class ").append(clazzName).append(" extends BaseExample {").append("\r\n");
        //添加一个用于记录查询参数的map
        sb.append("public Map<String,Object> params = new HashMap<String,Object>();\r\n");
        sb.append(addField("orderByClause", String.class.getName(), ""));
        sb.append(addField("groupByClause", String.class.getName(), ""));
        sb.append(addField("distinct", Boolean.TYPE.getName(), ""));
        sb.append(addField("oredCriteria", "Criteria", "List"));
        sb.append(addField("start", Integer.class.getName(), ""));
        sb.append(addField("limit", Integer.class.getName(), ""));

        sb.append(buildConstruct(clazzName));
        
        sb.append(buildConstructWithObject(clazzName));
        
        sb.append(buildMethod());

        sb.append(buildeInnerClassGeneratedCriteria(entity));
        sb.append(buildInnerClassCriteria());

        sb.append("}\r\n");
        return sb.toString();
    }

    public static String buildConstruct(String clazzName) {
        return "public " + clazzName + "() {" + "\r\n" +
                "oredCriteria = new ArrayList<Criteria>();\r\n" +
                "}\r\n";
    }
    
    public static String buildConstructWithObject(String clazzName) {
        return "public " + clazzName + "(" + Object.class.getName() + " o) {" + "\r\n" +
                "oredCriteria = new ArrayList<Criteria>();\r\n" +
                //初始化Limit
                "Object start = ReflectionUtils.getFieldValue(o, \"pageStart\");\r\n" +
                "if(null!=start){" + PRIFIX +
                "this.start = Integer.valueOf(start.toString())" + END_PRIFIX +
                "}" +
                //初始化Start
                "Object limit = ReflectionUtils.getFieldValue(o, \"pageSize\");\r\n" +
                "if(null!=limit){" + PRIFIX +
                "this.limit = Integer.valueOf(limit.toString())" + END_PRIFIX +
                "}" +
                "}\r\n";
    }

    public static String buildMethod() {
        return buildMethod;
    }

    public static String buildeInnerClassGeneratedCriteria(Class<?> entity) {
        StringBuilder sb = new StringBuilder();
        sb.append("public abstract static class GeneratedCriteria {\r\n");
        sb.append("public List<Criterion> criteria;\r\n");
        sb.append("public GeneratedCriteria() {\r\n");
        sb.append("   super();\r\n");
        sb.append("   criteria = new ArrayList<Criterion>();\r\n");
        sb.append("}\r\n");
        sb.append("public boolean isValid() {\r\n");
        sb.append("   return criteria.size() > 0;\r\n");
        sb.append("}\r\n");
        sb.append("public List<Criterion> getAllCriteria() {\r\n");
        sb.append("   return criteria;\r\n");
        sb.append("}\r\n");
        sb.append("public List<Criterion> getCriteria() {\r\n");
        sb.append("   return criteria;\r\n");
        sb.append("}\r\n");
        sb.append("public void addCriterion(String condition) {\r\n");
        sb.append("   if (condition == null) {\r\n");
        sb.append("      throw new RuntimeException(\"Value for condition cannot be null\");\r\n");
        sb.append("   }\r\n");
        sb.append("   criteria.add(new Criterion(condition));\r\n");
        sb.append("}\r\n");
        sb.append("public void addCriterion(String condition, Object value, String property) {\r\n");
        sb.append("   if (value == null) {\r\n");
        sb.append("      throw new RuntimeException(\"Value for \" + property + \" cannot be null\");\r\n");
        sb.append("   }\r\n");
        sb.append("   criteria.add(new Criterion(condition, value));\r\n");
        sb.append("}\r\n");
        sb.append("public void addCriterion(String condition, Object value1, Object value2, String property) {\r\n");
        sb.append("   if (value1 == null || value2 == null) {\r\n");
        sb.append("      throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");\r\n");
        sb.append("   }\r\n");
        sb.append("   criteria.add(new Criterion(condition, value1, value2));\r\n");
        sb.append("}\r\n");
        List<Field> fields = SqlProvider.getValidTableFiled(entity);
        for (Field f : fields) {
        	ExampleField exampleField = f.getAnnotation(ExampleField.class);
        	if(null == exampleField){
        		continue;
        	}
            if (!f.getName().contains("serialVersion")) {
                String type = f.getGenericType().toString().replace("class ", "").replace("interface", "");
                String fileName = StrUtils.toUpperCaseFirstOne(f.getName());
                Filed filed = f.getAnnotation(Filed.class);
                String humpName;
                if(null!=filed) {
                	humpName = ObjectUtils.isEmpty(filed.cloum())?f.getName():filed.cloum();
                }else {
                    humpName = StrUtils.humpToUnderline(SqlProvider.getCloumName(f), false);
                }
                sb.append("public Criteria and").append(fileName).append("IsNull() {").append("\r\n");
                sb.append("    addCriterion(\"").append(humpName).append(" is null\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("IsNotNull() {").append("\r\n");
                sb.append("    addCriterion(\"").append(humpName).append(" is not null\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("EqualTo(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" =\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("NotEqualTo(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" <>\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("GreaterThan(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" >\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("GreaterThanOrEqualTo(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" >=\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("LessThan(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" <\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("LessThanOrEqualTo(").append(type).append(" ").append(fileName).append(") {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" <=\", ").append(fileName).append(", \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("In(List<").append(type).append("> values) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" in\", values, \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("NotIn(List<").append(type).append("> values) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" not in\", values, \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("Between(").append(type).append(" value1,").append(type).append(" value2) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" between\", value1, value2, \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("NotBetween(").append(type).append(" value1,").append(type).append(" value2) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" not between\", value1, value2, \"").append(f.getName()).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("Like(").append(type).append(" value) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" like\", value, \"").append(fileName).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");

                sb.append("public Criteria and").append(fileName).append("NotLike(").append(type).append(" value) {").append("\r\n");
                sb.append("   addCriterion(\"").append(humpName).append(" not like\", value, \"").append(fileName).append("\")").append(";\r\n");
                sb.append("   return (Criteria) this;\r\n");
                sb.append("}\r\n");
            }
        }
        sb.append("}\r\n");
        return sb.toString();
    }

    public static String buildInnerClassCriteria() {
        return buildInnerClassCriteria;
    }


    public static String buildInnerClassCriterion() {
        return buildInnerClassCriterion;
    }

    public static String addField(String fieldName, String type, String collectionName) {
        StringBuilder sb = new StringBuilder();
        if (!StringUtils.isEmpty(collectionName)) {
            sb.append("private ").append(collectionName).append("<").append(type).append("> ").append(fieldName).append(";\r\n");
            sb.append("public ").append(collectionName).append("<").append(type).append("> get").append(StrUtils.toUpperCaseFirstOne(fieldName)).append("(){").append("\r\n");
        } else {
            sb.append("private ").append(type).append(" ").append(fieldName).append(";\r\n");
            sb.append("public ").append(type).append(" get").append(StrUtils.toUpperCaseFirstOne(fieldName)).append("(){").append("\r\n");
        }
        sb.append("   return this.").append(fieldName).append(";\r\n");
        sb.append("}\r\n");
        if (!StringUtils.isEmpty(collectionName)) {
            sb.append("public void set").append(StrUtils.toUpperCaseFirstOne(fieldName)).append("(").append(collectionName).append("<").append(type).append("> ").append(fieldName).append("){").append("\r\n");
        } else {
            sb.append("public void set").append(StrUtils.toUpperCaseFirstOne(fieldName)).append("(").append(type).append(" ").append(fieldName).append("){").append("\r\n");
        }
        sb.append("   this.").append(fieldName).append("=").append(fieldName).append(";\r\n");
        sb.append("}\r\n");
        return sb.toString();
    }


    private static final String buildInnerClassCriterion = """
            public static class Criterion implements Serializable{
                private String condition;
                private Object value;
                private Object secondValue;
                private boolean noValue;
                private boolean singleValue;
                private boolean betweenValue;
                private boolean listValue;
                private String typeHandler;
                public String getCondition() 
                   return condition;
                }
                public Object getValue() 
                   return value;
                }
                public Object getSecondValue() 
                   return secondValue;
                }
                public boolean isNoValue() 
                   return noValue;
                }
                public boolean isSingleValue() 
                   return singleValue;
                }
                public boolean isBetweenValue() 
                   return betweenValue;
                }
                public boolean isListValue() 
                   return listValue;
                }
                public String getTypeHandler() 
                   return typeHandler;
                }
                protected Criterion(String condition) 
                   super();
                   this.condition = condition;
                   this.typeHandler = null;
                   this.noValue = true;
                }
                protected Criterion(String condition, Object value, String typeHandler) 
                   super();
                   this.condition = condition;
                   this.value = value;
                   this.typeHandler = typeHandler;
                   if (value instanceof List<?>) 
                      this.listValue = true;
                   } else 
                      this.singleValue = true;
                   }
                }
                protected Criterion(String condition, Object value) 
                   this(condition, value, null);
                }
                protected Criterion(String condition, Object value, Object secondValue, String typeHandler) 
                   super();
                   this.condition = condition;
                   this.value = value;
                   this.secondValue = secondValue;
                   this.typeHandler = typeHandler;
                   this.betweenValue = true;
                }
                protected Criterion(String condition, Object value, Object secondValue) 
                   this(condition, value, secondValue, null);
                }
            };
         """;

         private static final String buildInnerClassCriteria = """
            public static class Criteria extends GeneratedCriteria implements Serializable {
                protected Criteria() {
                    super();
                }
            }
            """;
    private static  final String buildMethod="""
            public void or(Criteria criteria) {
                oredCriteria.add(criteria);
            }
            public Criteria or() {
               Criteria criteria = createCriteriaInternal();
               oredCriteria.add(criteria);
               return criteria;
            }
            public Criteria createCriteria() {
               Criteria criteria = createCriteriaInternal();
               if (oredCriteria.size() == 0) {
                oredCriteria.add(criteria);
               }
                return criteria;
            }
            protected Criteria createCriteriaInternal() {
               Criteria criteria = new Criteria();
               return criteria;
            }
            public void clear() {
               oredCriteria.clear();
               orderByClause = null;
               distinct = false;
            }
            """;
}